#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：model_study
@File    ：my_model_one.py
@Author  ：qsy
@Date    ：2024/10/15 14:23
'''

# 深度学习中的深度神经网络（DNN）模型。
# DNN是一种人工神经网络，适合于处理回归问题，能够通过学习输入特征与输出目标之间的非线性关系来进行预测。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
import matplotlib.pyplot as plt
import numpy as np
import joblib
from keras_tuner import RandomSearch

# 导入matlab 拟合的函数
from matlab_flow import predict_flow
from matlab_head import predict_head

# ---------------(1) 数据预处理---------------
# 读取excel文件
data = pd.read_excel('../data/恒转速数据.xlsx')
print(data.head())  # 打印前5行数据

# 取出速度和功率作为输入特征
X = data[['Speed', 'Power']].values
print(X[:5])  # 打印前5行特征数据

# 取出流量和扬程作为输出目标
Y = data[['Flow', 'Head']].values
print(Y[:5])  # 打印前5行目标数据

# 随机选择 20% 的数据作为测试集，保留全部的训练集
X_train = X
y_train = Y

# 生成随机索引，选择 20% 的测试数据
test_size = int(0.2 * len(X))
test_indices = np.random.choice(len(X), size=test_size, replace=False)

# 根据随机索引划分测试集
X_test = X[test_indices]
y_test = Y[test_indices]

# 打印数据集大小
print(f'训练集大小: {X_train.shape}, 测试集大小: {X_test.shape}')
print(f'测试集数据: \n{X_test}')

# ---------------(2) Matlab预测---------------
# 遍历测试集数据，调用matlab函数进行预测
y_pred_matlab = np.zeros((len(X_test), 2))
for i in range(len(X_test)):
    speed = X_test[i][0]
    power = X_test[i][1]
    flow = predict_flow(speed, power)
    head = predict_head(speed, power)
    y_pred_matlab[i][0] = flow
    y_pred_matlab[i][1] = head

# 绘制matlab预测结果与真实结果的散点图
plt.title('Matlab Fitting Predicted vs Real')
plt.scatter(y_test[:, 0], y_test[:, 1], label='real')
plt.scatter(y_pred_matlab[:, 0], y_pred_matlab[:, 1], label='pred')
plt.legend()
plt.show()

# 计算真实值跟matlab预测值的误差
error_matlab = np.mean(np.abs(y_test - y_pred_matlab))
print(f'Matlab预测的平均绝对误差: {error_matlab}')

# ---------------(3) 数据标准化---------------
scaler_X = StandardScaler()
X_train = scaler_X.fit_transform(X_train)  # 对训练集进行标准化
X_test = scaler_X.transform(X_test)  # 对测试集进行标准化

scaler_Y = StandardScaler()
y_train = scaler_Y.fit_transform(y_train)  # 对训练集目标值进行标准化
y_test = scaler_Y.transform(y_test)  # 对测试集目标值进行标准化

# 保存标准化器
joblib.dump(scaler_X, 'scaler_X.pkl')
joblib.dump(scaler_Y, 'scaler_Y.pkl')


# ---------------(4) 构建DNN模型---------------
# 隐藏层数量：
#
# 参数名称：num_layers
# 取值范围：从 1 到 10。
# 每层的单位数：
#
# 参数名称：units_i（i 代表层的索引，例如 units_0, units_1等）
# 取值范围：从 16 到 128，步长为 16。
# 优化器选择：
#
# 参数名称：optimizer
# 可选值：['adam', 'rmsprop']。
# 损失函数选择：
#
# 参数名称：loss
# 可选值：['mean_squared_error', 'mean_absolute_error']。
def build_model(hp):
    model = Sequential()
    # 调整隐藏层数量
    for i in range(hp.Int('num_layers', 1, 10)):
        model.add(Dense(units=hp.Int('units_' + str(i), min_value=16, max_value=128, step=16),
                        activation='relu'))

    model.add(Dense(2, activation='linear'))  # 输出层

    # 选择优化器
    optimizer = hp.Choice('optimizer', ['adam', 'rmsprop'])

    # 选择损失函数
    loss = hp.Choice('loss', ['mean_squared_error', 'mean_absolute_error'])

    model.compile(optimizer=optimizer,
                  loss=loss,
                  metrics=['mae'])
    return model


# ---------------(5) 超参数调优---------------
tuner = RandomSearch(build_model,
                     objective='val_mae',  # 优化目标val_mae 最小化平均绝对误差
                     max_trials=10,  # 最大搜索次数
                     executions_per_trial=1,  # 每次搜索的次数
                     directory='results',  # 保存搜索结果的路径
                     project_name='my_model'  # 项目名称
                     )

# 进行超参数搜索，epochs的范围设置为50到200
tuner.search(X_train, y_train, epochs=200,
             validation_split=0.2,
             batch_size=2)  # 添加batch_size的搜索

# 获取最佳模型
best_model = tuner.get_best_models(num_models=1)[0]

# 评估最佳模型
loss, mae = best_model.evaluate(X_test, y_test)
print(f'最佳模型在测试集上的损失: {loss}, 平均绝对误差: {mae}')

# 打印超参数搜索结果
# tuner.results_summary()
# 保存超参数搜索结果
# tuner.save('my_tuner.json')
# 打印最佳超参数
best_hp = tuner.get_best_hyperparameters(num_trials=1)[0]
print(f'最佳超参数: {best_hp}')

# ---------------(6) 模型预测---------------
y_pred = best_model.predict(X_test)

# 反标准化
y_pred = scaler_Y.inverse_transform(y_pred)
y_test = scaler_Y.inverse_transform(y_test)

# 绘制预测结果与真实结果的散点图
plt.title('Neural Network Predicted vs Real')
plt.scatter(y_test[:, 0], y_test[:, 1], label='real')
plt.scatter(y_pred[:, 0], y_pred[:, 1], label='pred')
plt.legend()
plt.show()

# 计算真实值跟预测值的误差
error_model = np.mean(np.abs(y_test - y_pred))
print(f'预测的平均绝对误差: {error_model}')

# 预测值跟真实值合成一个四维矩阵
result = np.concatenate((y_test, y_pred), axis=1)

# 打印预测值和真实值合成的矩阵
print(f'预测值和真实值合成的矩阵: \n{result}')

# ---------------(7) 单个值预测---------------
# 单个值预测
input_data = np.array([[1050, 205]])
input_data = scaler_X.transform(input_data)
output_data = best_model.predict(input_data)
output_data = scaler_Y.inverse_transform(output_data)
print(f'单个值预测结果: {output_data}')

# ---------------(8) 保存模型---------------
best_model.save('my_best_model.keras')
